﻿using System;

namespace SemanticEx
{
	public class Lexer
	{
		#region Constructors

		public Lexer(string input)
		{
			Input = input;
			Index = 0;
		}

		#endregion

		#region Properties

		public string Input { get; private set; }

		public int Index { get; private set; }

		#endregion

		#region Methods

		public void SkipWhiteSpace()
		{
			if (Index < Input.Length)
			{
				while (char.IsWhiteSpace(Input[Index]))
				{
					Index++;

					if (Index >= Input.Length)
					{
						break;
					}
				}
			}
		}

		public void ValidateEOF()
		{
			SkipWhiteSpace();
			if (Index < Input.Length)
			{
				throw new InvalidOperationException("Unable to process entire expression.");
			}
		}

		public void ThrowException(string text, params object[] args)
		{
			throw new InvalidOperationException(string.Format("Index: {0}, ", Index) + string.Format(text, args));
		}

		public bool IsMatch(string text)
		{
			SkipWhiteSpace();
			if (Index >= Input.Length)
			{
				return false;
			}
			if (Index + text.Length - 1 >= Input.Length)
			{
				return false;
			}
			return Input.Substring(Index, text.Length) == text;
		}

		public void Match(string text)
		{
			if (!IsMatch(text))
			{
				ThrowException("Unable to match '{0}'", text);
			}
			Index += text.Length;
		}

		public string ReadIdentifier()
		{
			SkipWhiteSpace();

			string ident = string.Empty;

			if (!char.IsLetter(Input[Index]) && (Input[Index] != '_'))
			{
				ThrowException("Unable to read identifier.");
			}
			ident += Input[Index];
			Index++;

			if (Index < Input.Length)
			{
				while (char.IsLetterOrDigit(Input[Index]) || (Input[Index] == '_'))
				{
					ident += Input[Index];
					Index++;

					if (Index >= Input.Length)
					{
						break;
					}
				}
			}

			return ident;
		}

		public string ReadString()
		{
			SkipWhiteSpace();

			if (!IsMatch("'"))
			{
				return null;
			}
			Match("'");
			var value = string.Empty;
			while (Input[Index] != '\'')
			{
				if (Input[Index] == '\\')
				{
					Index++;
				}
				value += Input[Index];
				Index++;
				if (Index >= Input.Length)
				{
					break;
				}
			}
			return value;
		}

		public double? ReadNumber()
		{
			SkipWhiteSpace();

			if (!char.IsNumber(Input[Index]) && (Input[Index] != '.'))
			{
				return null;
			}

			bool decimalFound = false;
			double value = 0;
			int divisor = 10;
			while (char.IsNumber(Input[Index]) || (Input[Index] == '.'))
			{
				if (Input[Index] == '.')
				{
					decimalFound = true;
					Index++;
					continue;
				}

				if (decimalFound)
				{
					value = value + (double)(Input[Index] - '0') / divisor;
					divisor *= 10;
				}
				else
				{
					value = (value * 10) + (int)(Input[Index] - '0');
				}
				Index++;

				if (Index >= Input.Length)
				{
					break;
				}
			}
			return value;
		}

		#endregion
	}
}